package org.lookahead.parent.common.utils;

import com.alibaba.fastjson.JSON;
import org.apache.commons.collections4.MapUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.lookahead.parent.apidoc.result.ResultException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.util.UriUtils;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.*;


/**
 * HttpClient工具类
 *
 * @author liushuoyang
 * @date 2019年1月30日
 */
public class ApacheHttpTool {

    /** 内部日志 */
    private static final Logger log = LoggerFactory.getLogger(ApacheHttpTool.class);
    /** HttpClient 对象 */
    private static CloseableHttpClient httpClient;
    /** 连接超时时间 */
    private static final int CONNECTION_TIMEOUT = 1000;
    /** 请求等待返回超时时间 */
    private static final int SOCKET_TIMEOUT = 3000;
    /** 连接池连接占满时等待超时时间 */
    private static final int CONN_MANAGER_TIMEOUT = 1000;
    /** 调用失败重试次数 */
    private static final int COUNT = 0;
    /** 连接池最大连接数 */
    private static final int MAX_TOTAL = 100;
    /** 同一主机的最大连接数 */
    private static final int MAX_PER_ROUTE = 20;
    /**
     * form表单请求头
     */
    public static final String HEADER_CONTENT_FORM = "application/x-www-form-urlencoded";
    /**
     * json请求头
     */
    public static final String HEADER_CONTENT_JSON = "application/json";
    /**
     * 默认字符集
     */
    public static final String DEFAULT_CHARSET = "UTF-8";


    static {
        /** 请求默认配置 */
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(CONNECTION_TIMEOUT)
                .setSocketTimeout(SOCKET_TIMEOUT)
                .setConnectionRequestTimeout(CONN_MANAGER_TIMEOUT)
                .build();

        /** 链接保活时间 */
        ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (httpResponse, httpContext) -> 20 * 1000;

        /** 连接池 */
        PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager();
        pool.setMaxTotal(MAX_TOTAL);
        pool.setDefaultMaxPerRoute(MAX_PER_ROUTE);

        /** 创建http实例 */
        httpClient = HttpClients.custom()
                .setConnectionManager(pool)
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(new MyHttpRequestRetryHandler())
                .setKeepAliveStrategy(connectionKeepAliveStrategy)
                .build();
    }

    /**
     * 发送get请求
     *
     * @param url    请求URL
     * @param header 请求头
     * @param params 请求参数
     * @return Result
     */
    public static HttpResult httpGet(String url, Map<String, String> header, Map<String, String> params) {
        return httpGet(url, header, params, null);
    }

    /**
     * 发送get请求
     *
     * @param url               请求URL
     * @param header            请求头
     * @param params            请求参数
     * @param connectionTimeout 链接超时时间
     * @param socketTimeout     socket超时时间
     * @return Result
     */
    public static HttpResult httpGet(String url, Map<String, String> header, Map<String, String> params,
                                 int connectionTimeout, int socketTimeout) {
        // 重新设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectionTimeout)
                .setSocketTimeout(socketTimeout)
                .setConnectionRequestTimeout(CONN_MANAGER_TIMEOUT)
                .build();
        return httpGet(url, header, params, requestConfig);
    }

    /**
     * 发送get请求
     *
     * @param url           请求URL
     * @param header        请求头
     * @param params        请求参数
     * @param requestConfig 请求配置
     * @return Result
     */
    public static HttpResult httpGet(String url, Map<String, String> header, Map<String, String> params, RequestConfig requestConfig) {
        // 拼装url
        url = url + appendUrlParams(params);
        url = UriUtils.encodeQuery(url, DEFAULT_CHARSET);
        HttpGet get = new HttpGet(url);
        if (requestConfig != null) {
            get.setConfig(requestConfig);
        }

        // 构造消息头
        if (MapUtils.isNotEmpty(header)) {
            for (String key : header.keySet()) {
                get.setHeader(key, header.get(key));
            }
        }

        long startTime = System.currentTimeMillis();
        int statusCode = 0;
        try {
            log.info("httpClient.get[url:{}], [request.header]:{}, [request.body]:{}", url, JSON.toJSONString(header), JSON.toJSONString(params));
            HttpResponse response = httpClient.execute(get);
            String responseBody = EntityUtils.toString(response.getEntity(), Charset.forName(DEFAULT_CHARSET));
            statusCode = response.getStatusLine().getStatusCode();
            log.info("httpClient.get[response]:{}, 请求耗时:[{}]ms", responseBody, (System.currentTimeMillis() - startTime));
            // 检验返回码
            if (statusCode != HttpStatus.SC_OK) {
                throw new ResultException("httpClient.get[url:" + url + "]返回状态异常,:" + statusCode + ":" + responseBody);
            }
            return new HttpResult(statusCode, responseBody);
        } catch (Exception e) {
            throw new ResultException(e.getMessage());
        } finally {
            get.releaseConnection();
        }
    }

    /**
     * 发送post form请求
     *
     * @param url    请求URL
     * @param header 请求头
     * @param params 请求参数
     * @return Result
     */
    public static HttpResult httpPostWithForm(String url, Map<String, String> header, Map<String, String> params) {
        return httpPostWithForm(url, header, params, null);
    }

    /**
     * 发送post form请求，自定义超时时间
     *
     * @param url    请求URL
     * @param header 请求头
     * @param params 请求参数
     * @return Result
     */
    public static HttpResult httpPostWithForm(String url, Map<String, String> header, Map<String, String> params,
                                              int connectionTimeout, int socketTimeout) {
        // 设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectionTimeout)
                .setSocketTimeout(socketTimeout)
                .setConnectionRequestTimeout(CONN_MANAGER_TIMEOUT)
                .build();
        return httpPostWithForm(url, header, params, requestConfig);
    }

    /**
     * 发送post form请求
     *
     * @param url     请求URL
     * @param header  请求头
     * @param params  请求参数
     * @return Result
     */
    public static HttpResult httpPostWithForm(String url, Map<String, String> header, Map<String, String> params, RequestConfig requestConfig) {
        HttpPost post = new HttpPost(url);
        if (requestConfig != null) {
            post.setConfig(requestConfig);
        }
        // 构造消息头
        post.setHeader("Content-type", HEADER_CONTENT_FORM);
        if (MapUtils.isNotEmpty(header)) {
            for (String key : header.keySet()) {
                post.setHeader(key, header.get(key));
            }
        }
        // 构建请求参数
        List<NameValuePair> paramList = new ArrayList<>();
        if (MapUtils.isNotEmpty(params)) {
            Set<String> keySet = params.keySet();
            for (String key : keySet) {
                paramList.add(new BasicNameValuePair(key, params.get(key)));
            }
        }
        StringEntity entity = new UrlEncodedFormEntity(paramList, Charset.forName(DEFAULT_CHARSET));
        entity.setContentEncoding(DEFAULT_CHARSET);
        // 发送form格式的数据请求
        entity.setContentType(HEADER_CONTENT_FORM);
        post.setEntity(entity);

        long startTime = System.currentTimeMillis();
        int statusCode = 0;
        try {
            log.info("httpClient.post[url:{}],[request.header]:{},[request.body]:{}", url, JSON.toJSONString(header), JSON.toJSONString(params));
            HttpResponse response = httpClient.execute(post);
            String responseBody = EntityUtils.toString(response.getEntity());
            statusCode = response.getStatusLine().getStatusCode();
            log.info("httpClient.post[response]:{}, 请求耗时:[{}]ms", responseBody, (System.currentTimeMillis() - startTime));
            // 检验返回码
            if (statusCode != HttpStatus.SC_OK) {
                throw new ResultException("httpClient.post[url:" + url + "]返回状态异常,:" + statusCode + ":" + responseBody);
            }
            return new HttpResult(statusCode, responseBody);
        } catch (Exception e) {
            throw new ResultException(e.getMessage());
        } finally {
            post.releaseConnection();
        }
    }

    /**
     * 发送post json请求
     *
     * @param url    请求URL
     * @param header 请求头
     * @param params 请求参数
     * @return Result
     */
    public static HttpResult httpPostWithJson(String url, Map<String, String> header, Object params) {
        return httpPostWithJson(url, header, params, null);
    }

    /**
     * 发送post json请求
     *
     * @param url    请求URL
     * @param header 请求头
     * @param params 请求参数
     * @return Result
     */
    public static HttpResult httpPostWithJsonWithTimeOut(String url, Map<String, String> header, Object params,
                                                     int connectionTimeout, int socketTimeout) {
        // 设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectionTimeout)
                .setSocketTimeout(socketTimeout)
                .setConnectionRequestTimeout(CONN_MANAGER_TIMEOUT)
                .build();
        return httpPostWithJson(url, header, params, requestConfig);
    }

    /**
     * 发送post json请求
     *
     * @param url    请求URL
     * @param header 请求头
     * @param params 请求参数
     * @return Result
     */
    public static HttpResult httpPostWithJson(String url, Map<String, String> header, Object params, RequestConfig requestConfig) {
        HttpPost post = new HttpPost(url);
        if (requestConfig != null) {
            post.setConfig(requestConfig);
        }
        // 构造消息头
        post.setHeader("Content-type", HEADER_CONTENT_JSON);
        if (MapUtils.isNotEmpty(header)) {
            for (String key : header.keySet()) {
                post.setHeader(key, header.get(key));
            }
        }
        // 构造消息体
        String paramsStr;
        if (params == null) {
            paramsStr = "{}";
        } else if (params instanceof String) {
            paramsStr = (String) params;
        } else {
            paramsStr = JSON.toJSONString(params);
        }
        // 构建消息实体
        StringEntity entity = new StringEntity(paramsStr, Charset.forName(DEFAULT_CHARSET));
        entity.setContentEncoding(DEFAULT_CHARSET);
        // 发送Json格式的数据请求
        entity.setContentType(HEADER_CONTENT_JSON);
        post.setEntity(entity);

        long startTime = System.currentTimeMillis();
        int statusCode = 0;
        try {
            log.info("httpClient.post[url:{}], [request.header]:{}, [request.body]:{}", url, JSON.toJSONString(header), JSON.toJSONString(params));
            HttpResponse response = httpClient.execute(post);
            String responseBody = EntityUtils.toString(response.getEntity());
            statusCode = response.getStatusLine().getStatusCode();
            log.info("httpClient.post[response]:{}, 请求耗时:[{}]ms", responseBody, (System.currentTimeMillis() - startTime));
            // 检验返回码
            if (statusCode != HttpStatus.SC_OK) {
                throw new ResultException("httpClient.post[url:" + url + "]返回状态异常,:" + statusCode + ":" + responseBody);
            }
            return new HttpResult(statusCode, responseBody);
        } catch (Exception e) {
            throw new ResultException(e.getMessage());
        } finally {
            post.releaseConnection();
        }
    }

    /**
     * 构建唯一会话Id
     *
     * @return String
     */
    private static String getSessionId() {
        UUID uuid = UUID.randomUUID();
        String str = uuid.toString();
        return str.substring(0, 8) + str.substring(9, 13) + str.substring(14, 18) + str.substring(19, 23) + str.substring(24);
    }

    /**
     * 设置重试
     */
    static class MyHttpRequestRetryHandler implements HttpRequestRetryHandler {
        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            // 超过重试次数，就放弃
            if (executionCount > COUNT) {
                return false;
            }
            if (exception instanceof NoHttpResponseException) {
                log.info("没有响应，ApacheHttpTool重试1次");
                return true;
            } else if (exception instanceof ConnectTimeoutException) {
                log.info("连接超时，ApacheHttpTool重试1次");
                return true;
            } else if (exception instanceof SocketTimeoutException) {
                log.info("连接或读取超时，ApacheHttpTool重试1次");
                return true;
            } else if (exception instanceof UnknownHostException) {
                log.info("找不到服务器，ApacheHttpTool重试1次");
                return true;
            } else if (exception instanceof SSLHandshakeException) {
                log.info("SSLHandshakeException，ApacheHttpTool重试1次");
                return false;
            } else if (exception instanceof InterruptedIOException) {
                log.info("InterruptedIOException，ApacheHttpTool重试1次");
                return false;
            } else if (exception instanceof SSLException) {
                log.info("SSLException，ApacheHttpTool重试1次");
                return false;
            } else {
                log.error("未记录的请求异常：" + exception.getClass());
            }
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            // 如果请求是幂等的，则重试
            return !(request instanceof HttpEntityEnclosingRequest);
        }
    }

    /**
     * 拼接请求参数，此方法不对返回值进行编码
     *
     * @param params 请求参数
     * @return String
     */
    public static String appendUrlParams(Map<String, String> params) {
        if (params == null || MapUtils.isEmpty(params)) {
            return "";
        } else {
            return "?" + appendParams(params);
        }
    }

    /**
     * 拼接请求参数，此方法不对返回值进行编码，不包含'?'
     *
     * @param params 请求参数
     * @return String
     */
    public static String appendParams(Map<String, String> params) {
        if (params == null || MapUtils.isEmpty(params)) {
            return "";
        }
        Set<String> paramKey = params.keySet();
        Iterator<String> it = paramKey.iterator();
        List<String> paramList = new ArrayList<>();
        while (it.hasNext()) {
            String key = it.next();
            String value = params.get(key);
            paramList.add(key + "=" + value);
        }
        return String.join("&", paramList);
    }
}
